//
//  TransfersQueue.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/03/05.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "TransfersQueue.h"
#import "EngineDefaults.h"

#import "Transfers.h"
#import "Peers.h"

@interface TransfersQueue (Abilities)
- (unsigned)countTransfersExitingForPeer:(Peer *)peer;
- (unsigned)countTransfersExitingTotal;
- (unsigned)countPeersExitingTotal;
- (BOOL)canTransferExitNow:(Transfer *)transfer;
- (void)startExitingTransfer:(Transfer *)transfer;
- (void)updateTransferQueueLocations;
@end

@interface TransfersQueue (QueueModes)
- (unsigned)indexOfFirstDuplicatePeer:(Transfer *)transfer privileged:(BOOL)priv;
- (void)indexOfFirstNonPrivilegedPeer:(Transfer *)transfer;
@end

@implementation TransfersQueue
- (id)init {
	if(self = [super init]) {
		_queue = [[NSMutableArray alloc] init];
		_transfersExiting = [[NSMutableArray alloc] init];
		_transferExiting = nil;
		_type = QueueTypeByUser;
	}
	return self;
}

- (void)dealloc {
	[_transferExiting release];
	[_transfersExiting release];
	[_queue release];
	[super dealloc];
}

#pragma mark private

- (unsigned)countCeilingTransfersExitingPerPeer { 
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	unsigned uploads = UINT_MAX;
	if([defaults boolForKey:SolarSeekDefaultCapCountUploads])
		uploads = [defaults integerForKey:SolarSeekDefaultCountUploadsPerPeerCap];
	return uploads;
}

- (unsigned)countCeilingTransfersExitingTotal {
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	unsigned uploads = UINT_MAX;
	if([defaults boolForKey:SolarSeekDefaultCapCountUploads])
		uploads = [defaults integerForKey:SolarSeekDefaultCountUploadsTotalCap];
	return uploads;
}

- (unsigned)countCeilingPeersExitingTotal {
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	unsigned peers = UINT_MAX;
	if([defaults boolForKey:SolarSeekDefaultCapCountUploads])
		peers = [defaults integerForKey:SolarSeekDefaultCountPeersTotalCap];
	return peers;
}

- (unsigned)countTransfersQueued { return [_queue count]; }
		
		
- (unsigned)countTransfersExitingForPeer:(Peer *)peer {
	NSEnumerator *e = [_transfersExiting objectEnumerator];
	Transfer *t;
	unsigned count=0;
	while(t = [e nextObject]) {
		if([[t peer] isEqual:peer])
			count++;
	}
	return count;
}

- (unsigned)countTransfersExitingTotal { return [_transfersExiting count]; }
- (unsigned)countPeersExitingTotal {
	NSEnumerator *e = [_transfersExiting objectEnumerator];
	NSMutableArray *a = [NSMutableArray arrayWithCapacity:[self countCeilingPeersExitingTotal]];
	Transfer *t;
	while(t = [e nextObject]) {
		if([a containsObject:[t peer]]==NO)
			[a addObject:[t peer]];
	}
	return [a count];
}

- (BOOL)canTransferExitNow:(Transfer *)transfer {
	return [self countTransfersExitingTotal] < [self countCeilingTransfersExitingTotal] &&
			[self countPeersExitingTotal] < [self countCeilingPeersExitingTotal] &&
			[self countTransfersExitingForPeer:[transfer peer]] < [self countCeilingTransfersExitingPerPeer];
}

#pragma mark queue modes

- (void)queueBeforeFirstDuplicatePeerAfterLastPeerMatchingTransfer:(Transfer *)transfer {
	unsigned i = 0;

	while(i < [_queue count] && [[[_queue objectAtIndex:i] peer] privileges] > [[transfer peer] privileges])
		i++;
		
	NSMutableSet *peers = [NSMutableSet set];
	while(i < [_queue count]) {
		Peer *p = [[_queue objectAtIndex:i] peer];
		if([p isEqual:[transfer peer]])
			[peers removeAllObjects];
		else {
			unsigned count = [peers count];
			[peers addObject:p];
			if(count == [peers count])
				break;
		}
		i++;
	}

	i = i>[_queue count]?[_queue count]:i;
		
	[_queue insertObject:transfer atIndex:i];
}
		
- (void)queueAfterLastTransferWithPrivilegesMatchingTransfer:(Transfer *)transfer {
	unsigned i = 0;
	
	while(i < [_queue count] && [[[_queue objectAtIndex:i] peer] privileges] > [[transfer peer] privileges])
		i++;
	
	[_queue insertObject:transfer atIndex:i];
}

#pragma mark KVO

- (Transfer *)transferExiting { return _transferExiting; }


#pragma mark actions

- (void)dequeueTransfer:(Transfer *)transfer {
//	NSLog(@"Dequeuing transfer %@",transfer);
	
	if(transfer) {
		[_transfersExiting removeObject:transfer];
		[_queue removeObject:transfer];
	}
	
	if ( [self countPeersExitingTotal] < [self countCeilingPeersExitingTotal] &&
		 [self countTransfersExitingTotal] < [self countCeilingTransfersExitingTotal]) {
		//find a transfer to start!!
		NSEnumerator *e = [_queue objectEnumerator];
		Transfer *t;
		while(t = [e nextObject]) {
			if([self countTransfersExitingForPeer:[t peer]] < [self countCeilingTransfersExitingPerPeer]) {
				[self startExitingTransfer:t];
				break;
			}
		}
	}	
}


- (void)exemptTransfer:(Transfer *)transfer {
	[self startExitingTransfer:transfer];
	[_queue removeObject:transfer];
}


- (unsigned)placeInQueue:(Transfer *)transfer {
	return [_queue indexOfObject:transfer];
}


- (BOOL)queueTransfer:(Transfer *)transfer {
	if([_queue containsObject:transfer]==YES)
		return YES;
	
	if([self canTransferExitNow:transfer]==YES) {
		[self startExitingTransfer:transfer];
		return NO;
	}
	else {
		[transfer setState:TransferStateIdle description:@"Queued"];
		switch(_type) {
			case QueueTypeByTransfer:
				[self queueAfterLastTransferWithPrivilegesMatchingTransfer:transfer];
				break;
			case QueueTypeByUser:
			default:
				[self queueBeforeFirstDuplicatePeerAfterLastPeerMatchingTransfer:transfer];
				break;
		}
		[self updateTransferQueueLocations];
		return YES;
	}
}

- (void)startExitingTransfer:(Transfer *)transfer {
	[_queue removeObject:transfer];
	[self updateTransferQueueLocations];
	if([transfer state]==TransferStateIdle)
		[transfer setLocationQueue:0];		
	[self willChangeValueForKey:@"transferExiting"];
	[_transferExiting release];
	_transferExiting = [transfer retain];
	[_transfersExiting addObject:transfer];
	[self didChangeValueForKey:@"transferExiting"];
}

- (void)updateTransferQueueLocations {
	int i;
	Transfer *t;
	for(i=0; i<[_queue count];i++) {
		t = [_queue objectAtIndex:i];
		if([t state]==TransferStateIdle)
			[t setLocationQueue:i+1];
	}
}

@end
